# Django 一对一、一对多、多对多 操作
from django.http import HttpResponse
from django.db.models import F, Q
from index.models import Student, StudentDetail, Class, Teacher
from django.db.models.functions import Concat
from django.db.models import Value
import datetime
import json


def operatedb(request):
    # 一对一  Student ---> StudentDetail
    # 正向查询(由学生信息表查询学生详情表)
    # 调用学生对象的detail属性，该属性表示外键映射副表中对应的对象
    stu = Student.objects.first()
    print(stu.detail.email, '---->', stu.detail.memo)
    stu1 = Student.objects.filter(id__gt=2)[0].detail
    print(stu1.email, '---->', stu1.memo)
    # 过滤学生信息表查询数据，过滤参数为 "主表名__条件"
    stu2 = StudentDetail.objects.filter(student__id__gt=1)
    print(stu2)
    # 反向查询(由学生详情表查询学生信息表)
    # 副表要调用对应主表的信息对象，需要使用  .主表名(全小写)
    det = StudentDetail.objects.get(id=1).student
    print(det.id, '---->', det.sname)
    # 主表调用副表，直接使用添加外键的属性detail就行，这个属性就是副表中对应的一条数据对象
    det = Student.objects.filter(detail__id__exact=1)
    print(det[0].id, '---->', det[0].sname)
    det = Student.objects.filter(detail__id=1)
    print(det[0].id, '---->', det[0].sname)

    # 一对多  Student ---> Class
    # 正向查询（由学生表查询班级表）
    stu = Student.objects.first()
    print(stu.cid_id, stu.cid.cname, '---->', stu.cid.id, stu.cid.cname)
    # 使用主表添加的外键属性，获取副表中的对应数据对象
    stu1 = Student.objects.get(id=1).cid
    print(stu1.cname, '---->', stu1.cdata)
    # 副表过滤主表条件的参数，使用student可以调用主表对象，student__id是调用的student对象的id进行筛选
    stu2 = Class.objects.filter(student__id=1)
    print(stu2[0].cdata, '---->', stu2[0].cname)
    # 反向查询（由班级表查询学生表）
    cls = Class.objects.first().student_set.all()
    print(cls)
    # 注意：如果不在外键的字段中设置related_name的话，默认就用表名_set。如果设置了related_name=”students”，反向查询时可直接使用students进行反向查询。以上一句可以使用cls.students.all()代替
    # 副表调用主表信息，使用主表名(全小写)__set ,这个其实是一个副表对象在主表中的映射管理器，类似Student.objtects中的objects，这儿objects是控制管理器
    cls1 = Class.objects.get(id=1).student_set.all()
    print(cls1)
    print(cls1[0].sname)
    cls2 = Student.objects.filter(cid__id=1)  # 与cls1等价
    print(cls2)

    # 多对多    Teacher --> Class
    # 正向查询（由老师表查询班级表）
    tea = Teacher.objects.first()
    print(tea.id, '---->', tea.tname)
    tea1 = Teacher.objects.all()
    print(tea1)
    # 多对多其实可以对应一对多，两者大体一致，只不过主表的外键属性和副表的teacher__set 都是相应的映射管理器，它内部其实都是对应的中间表的进行的关联映射
    tea2 = Teacher.objects.get(id=1).cid.all()
    print(tea2[0].cname, '---->', tea2[0].cdata)
    tea3 = Class.objects.filter(teacher__id=1)
    print(tea3[0].cname, '---->', tea3[0].cdata)
    # 反向查询（由班级表反向查询老师表）
    tea4 = Class.objects.get(id=1).teacher_set.all()
    print(tea4[0].tname)
    # 使用到了Q查询，当使用或的条件查询语句时使用，并且F,Q查询与关键字参数一起使用时，F,Q查询必须写到关键字参数前面
    tea5 = Teacher.objects.filter(Q(cid__id=1) | ~Q(cid__id=2))
    print(tea5)

    # 多对多关系模型添加移除中间表关系（本质是给中间表添加，移除数据） Teacher --> Class
    # 添加
    Teacher.objects.get(id=5).cid.add(*Class.objects.filter(id__gt=3))
    # 移除
    Teacher.objects.get(id=5).cid.remove(*Class.objects.filter(id__gt=1))
    # 清空对象关系
    Teacher.objects.get(id=5).cid.clear()
    # 重新设置关系，添加新关系，删除多余关系
    Teacher.objects.get(id=5).cid.set(
        list(Class.objects.filter(id__gt=5)))  # 参数为一个可迭代对象就可以
    # 注意：在上面文件中，提到的映射管理器，其实形似objects这个控制管理器，同样可以使用all(),values(),value_list()方法，对结果进行查询。当然这个映射管理器也可以添加映射，在多对多中通过映射管理器的add方法添加一个新的关系映射，在一对多中通过映射管理器的create方法，可以添加新的副表数据。以下为测试实例：

    # 在id=3的班级添加一个学生，该学生会被自动创建，并且添加了班级和学生之间的映射关系
    # Class.objects.get(id=3).student_set.create(sname='Daniel', detail_id=8)
    # 在多对多中通过add添加外键映射关系，并且在中间表中生成对应关系数据
    Class.objects.get(id=3).teacher_set.add(Teacher.objects.get(id=4))

    # F查询：F()的实例可以在查询中引用字段，来比较同一个model 实例中两个不同字段的值
    # F可以帮我们取到表中某个字段对应的值来当作我的筛选条件，而不是我认为自定义常量的条件了，实现了动态比较的效果。F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。基于此可以对表中的数值类型进行数学运算
    relsult = Student.objects.filter(
        id__gt=F('cid_id'))  # 查询id 大于等于cid_id 最大值的数据
    print(relsult)
    Class.objects.update(cdata=F('cdata')-2)
    Class.objects.update(cdata=F('cdata')+2)  # 给Class表中的cdata值加2

    # 引申：如果要修改char字段不能用上面对数值类型的操作
    # 如：把Student表中所有sname后面加上'001',(这个时候需要对字符串进行拼接Concat操作，并且要加上拼接值Value，Concat表示进行字符串的拼接操作，参数位置决定了拼接是在头部拼接还是尾部拼接)
    # relsult = Student.objects.update(sname=Concat(F('sname'),Value('001')))

    # Q查询：filter() 等方法中逗号隔开的条件是与的关系，如果你需要执行更复杂的查询（例如OR语句）
    # 组合& 和|  操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时，Q 对象可以使用~ 操作符取反，这允许组合正常的查询和取反(NOT) 查询。
    result = Student.objects.filter(Q(id__gt=3) | Q(
        detail_id__lt=7))  # 查出Student表中id 大于3 或 detail_id小于7
    print(result[0].id, '---->', result[0].sname)
    # 对条件包裹一层Q时候，filter即可支持交叉并的比较符
    result = Student.objects.filter(Q(id=8) & Q(
        detail_id=8))  # 查出id 为8并且detail_id为8的信息
    print(result[0].sname)
    result = Student.objects.filter(Q(id=8) & ~Q(
        detail_id=8))  # 查出id 为8并且detail_id不为8的信息
    print(result)
    result = Student.objects.filter(
        Q(id__gt=5), sname__contains='el')  # 查出sname中包含'el'，并且id 大于5的信息
    print(result)
    # 注意：查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数（关键字参数或Q 对象）都将"AND”在一起。但是，如果出现Q 对象，它必须位于所有关键字参数的前面。

    # 常用QuerySet操作：
    # 1. 支持链式调用的接口
    # all：使用频率比较高，相当于SELECT * FROM table 语句，用于查询所有数据。
    print(Student.objects.all()[0].sname)
    # filter：使用频率比较高，根据条件过滤数据，常用的条件基本上字段等于、不等于、大于、小于。当然，还有其他的，比如能修改成产生LIKE查询的：Model.objects.filter(content__contains="条件")。
    print(Student.objects.filter(sname__contains='Daniel')[0].sname)
    # exclude：与filter是相反的逻辑
    print(Student.objects.exclude(sname__contains='Daniel')[0].sname)
    # reverse：将QuerySet中的结果倒叙排列
    # distinct：用来进行去重查询，产生SELECT DISTINCT这样的SQL查询
    # none：返回空的QuerySet

    # 2. 不支持链式调用的接口：
    # get接口：用于查询，存在返回对应的实例，不存在，则抛出DoesNotExist异常
    try:
        result = Student.objects.get(id=12)
    except Student.DoesNotExist:
        print('没有id=12的数据')
    # create接口：直接创建一个Model对象
    Class.objects.create(cname='Daniel', cdata=datetime.datetime.now().strftime(
        '%Y-%m-%d'))  # strftime('%Y-%m-%d %H:%M:%S')
    # get_or_create接口：根据条件查找，如果没有查找到，就调用create创建
    Teacher.objects.get_or_create(id=50, defaults={'tname': 'Daniel'})
    # update_or_create接口：同get_or_create，只是用来做更新操作
    Teacher.objects.update_or_create(id=50, defaults={'tname': 'Daniels'})
    # count接口：用于返回Queryset有多少条记录
    print('查询记录数：', Student.objects.all().count())
    # latest接口：用于返回最新一条记录，但需要在Model的Meta中定义：get_latest_by = <用来排序的字段>
    print(Teacher.objects.filter(tname='Daniel').latest('id'))
    # earliest接口：同上，返回最早的一条记录
    print(Teacher.objects.filter(tname='Daniel').earliest('id'))
    # first接口：从当前Queryset记录中获取第一条
    print(Student.objects.all().first())
    # last接口：同上，获取最后一条
    print(Student.objects.all().last())
    # exists接口：返回True或者False，只需要判断Queryset是否有数据用这个接口最合适
    print(Teacher.objects.filter(tname='Daniel1').exists())
    # bulk_create接口：同create，用来批量创建记录
    # in_bulk接口：批量查询，接收两个参数id_ list和filed_ name。可以通过Post.objects. in_ bulk([1, 2, 3])查询出id为1、2、3的数据，返回结果是字典类型，字典类型的key为查询条件。返回结果示例: {1: <Post 实例1>, 2: <Post实例2>，3:<Post实例3>}
    print(Teacher.objects.in_bulk([1, 2, 3]))
    # update接口： 用来根据条件批量更新记录
    Teacher.objects.filter(id=1).update(tname='Daniel')
    # delete接口： 同update，这个接口是用来根据条件批量删除记录。update和delete都会出发Django的signal
    Teacher.objects.filter(id=10).delete()
    # values接口:当我们明确知道只需要返回某个字段的值，不需要Model实例时，可以使用
    result = Teacher.objects.all().values()
    print(result)
    result_list = list(result)  # queryset转为list
    result_json = json.dumps(result_list)  # list 转为json
    # values_list接口：同values，但直接返回的是包含tuple的Queryset
    result = Teacher.objects.all().values_list()
    print(result)

    # 3.进阶接口
    # defer：把不需要展示的字段做延迟加载。比如说，需要获取到文章中除正文外的其他字段，就可以通过posts = Post.objects.all() .defer('content'),这样拿到的记录中就不会包含content部分。
    # 但是当我们需要用到这个字段时，在使用时会去加载。代码:
    # 1 posts = Post.objects.all().defer('content')
    result = Class.objects.all().defer('cdata')
    for i in result:  # 此时会执行数据库查询
        print(i.cdata)  # 此时会执行数据查询，获取到cdata
    # 当不想加载某个过大的字段时(如text类型的字段)，会使用defer,但是上面的演示代产生N+1的查询问题，在实际使用时千万要注意!
    # 注意：上面的代码是个不太典型的 N+1查询的问题， 一般情况下 由外键查询产生的N+1问题比较多，即一条查询请求返回N条数据，当我们操作数据时，又会产生额外的请求。这就是N+1问题，所有的ORM框架都存在这样的问题。
    # only：同defer接口刚好相反， 如果只想获取到所有的title记录，就可以使用only,只获取title的内容，其他值在获取时会产生额外的查询。

    # select_related：这就是用来解决外键产生的N+1问题的方案。我们先来看看什么情况下会产生这个问题:
    # posts = Post.objects.all ()
    # for post in posts:  #产生数据库查询
    #     print (post.owner)  #产生额外的数据库查询
    # 代码同上面类似，只是这里用的是owenr(是关联表)。它的解决方法就是用select_ related接口:

    # post = Post.objects.all() .select_related('category')
    # for post in posts: # 产生数据库查询，category数据也会一次性查询出来
    #     print (post.category)
    # 当然，这个接口只能用来解决一对多的关联关系。对于多对多的关系,还得使用下面的接口。

    # prefetch_related：针对多对多关系的数据，可以通过这个接口来避免N+1查询。比如，post和tag的关系可以通过这种方式来避免:
    # posts = Post.objects.all().prefetch_related('tag')
    # for post in posts:#产生两条查询语句，分别查询post和tag
    #     print(post.tag.al1())

    # 4. 常用的字段查询
    # contains：包含，用来进行相似查询。
    # icontains：同contains,只是忽略大小写。
    # exact：精确匹配（与等号类似）。
    # iexact：同exact,忽略大小写。
    # in：指定某个集合，比如Post.objects.filter(id__in=[1, 2, 3])相当于SELECT FROM table WHERE IN (1, 2, 3);。
    # gt：大于某个值。比如：Post.objects.filter(id__gt=1)。注意:是__gt
    # gte：大于等于某个值。
    # lt：小于某个值。
    # lte：小于等于某个值。
    # startswith：以某个字符串开头，与contains类似，只是会产生LIKE '<关键词>%'这样的SQL。
    # istartswith：同startswith, 忽略大小写。
    # endswith：以某个字符串结尾。
    # iendswith：同endswith,忽略大小写。
    # range：范围查询，多用于时间范围，如Post.objects.filter(created_time__range= ('2018-05-01','2018-06-01'))会产生这样的查询: SELECT .. . WHERE created_ time BETWEEN '2018-05-01' AND '2018-06-01' ;。
    return HttpResponse('Success')

    # filter
    # 条件选取querySet的时候，filter表示=，exclude表示!=。querySet.distinct() 去重复
    # __exact 精确等于 like 'aaa'
    # __iexact 精确等于 忽略大小写 ilike 'aaa'
    # __contains 包含 like '%aaa%'
    # __icontains 包含 忽略大小写 ilike '%aaa%'，但是对于sqlite来说，contains的作用效果等同于icontains。
    # __gt 大于
    # __gte 大于等于
    # __lt 小于
    # __lte 小于等于
    # __in 存在于一个list范围内
    # __startswith 以...开头
    # __istartswith 以...开头 忽略大小写
    # __endswith 以...结尾
    # __iendswith 以...结尾，忽略大小写
    # __range 在...范围内
    # __year 日期字段的年份
    # __month 日期字段的月份
    # __day 日期字段的日
    # __isnull=True/False
